package com.mewp.util;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

/**
 * Redis连接池工厂类
 *
 * @author mewp
 */
@Slf4j
public class JedisConnectFactory {
    /**
     * 最大连接数
     */
    public static final int MAX_TOTAL = 50;
    /**
     * 最大空闲连接数
     */
    public static final int MAX_IDLE = 50;
    /**
     * 最小空闲连接数
     */
    public static final int MIN_IDLE = 8;

    private static JedisPool JEDIS_POOL;

    static {
        // 创建连接池
        buildPool();
        // 预热连接池
        hotPool();
    }

    /**
     * 创建连接池
     */
    private static void buildPool() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接数，默认值为8。
        jedisPoolConfig.setMaxTotal(MAX_TOTAL);
        // 最大空闲连接数，默认值为8。
        jedisPoolConfig.setMaxIdle(MAX_IDLE);
         /*
             最小空闲连接数，默认值为0。
             如果JedisPool开启了空闲连接的有效性检测，如果空闲连接无效，就销毁。销毁连接后，连接数量就少了，
             如果小于minIdle数量，就新建连接，维护数量不少于minIdle的数量。
             minIdle确保了线程池中有最小的空闲Jedis实例的数量。
          */
        jedisPoolConfig.setMinIdle(MIN_IDLE);
        // 当资源池用尽后，调用者是否要等待，默认值为true。当为true时，maxWaitMillis才会生效。
        jedisPoolConfig.setBlockWhenExhausted(true);
        // 当资源池连接用尽后，调用者的最大等待时间（单位为毫秒）。默认值为-1，表示永不超时，不建议使用默认值。
        jedisPoolConfig.setMaxWait(Duration.ofMillis(200));
        /*
            向资源池借用连接时，是否做有效性检测（ping命令），如果是无效连接，会被移除，默认值为false，表示不做检测。
            如果为true，则得到的Jedis实例均是可用的。在业务量小的应用场景，建议设置为true，确保连接可用；在业务量很大的应用场景，
            建议设置为false（默认值），少一次ping命令的开销，有助于提升性能。
         */
        jedisPoolConfig.setTestOnBorrow(false);
        /*
            向资源池归还连接时，是否做有效性检测（ping命令），如果是无效连接，会被移除，默认值为false，表示不做检测。
            同样，在业务量很大的应用场景，建议设置为false（默认值），少一次ping命令的开销。
         */
        jedisPoolConfig.setTestOnReturn(false);
        /*
            如果为true，表示用一个专门的线程对空闲的连接进行有效性的检测扫描，如果连接的有效性检测失败，即表示监测到无效连接，会从资源池中移除。
            默认值为true，表示进行空闲连接的检测。
            这个选项存在一个附加条件，需要空闲扫描间隔时间配置项timeBetweenEvictionRunsMillis的值大于0；否则，testWhileIdle不会生效。
         */
        jedisPoolConfig.setTestWhileIdle(true);
        // 表示两次空闲连接扫描的间隔时间，默认为30000毫秒
        jedisPoolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(30000));
        /*
            表示一个Jedis连接至少停留在空闲状态的最短时间，然后才能被空闲连接扫描线程进行有效性检测，默认值为60000毫秒，即60秒。
            也就是说在默认情况下，一条Jedis连接只有在空闲60秒后，才会参与空闲线程的有效性检测。
            这个选项存在一个附加条件，需要在timeBetweenEvictionRunsMillis大于0时才会生效。
            也就是说，如果不启动空闲检测线程，这个参数也没有什么意义。
         */
        jedisPoolConfig.setMinEvictableIdleTime(Duration.ofMillis(60000));
        // 表示空闲检测线程每次最多扫描的Jedis连接数，默认值为-1，表示扫描全部的空闲连接。
        jedisPoolConfig.setNumTestsPerEvictionRun(-1);
        // 是否开启JMX监控，默认值为true，建议开启。
        jedisPoolConfig.setJmxEnabled(true);

//        JEDIS_POOL = new JedisPool(jedisPoolConfig, "192.168.0.210", 6379, 1000, "123456");
        JEDIS_POOL = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379, 1000, "123456");
    }

    /**
     * 连接池的预热
     */
    private static void hotPool() {
        long start = System.currentTimeMillis();
        List<Jedis> minIdleJedisList = new ArrayList<>(MIN_IDLE);
        Jedis jedis;
        for (int i = 0; i < MIN_IDLE; i++) {
            try {
                jedis = JEDIS_POOL.getResource();
                minIdleJedisList.add(jedis);
                jedis.ping();
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        for (int i = 0; i < MIN_IDLE; i++) {
            try {
                jedis = minIdleJedisList.get(i);
                jedis.close();
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        long end = System.currentTimeMillis();
        log.info("hotPool 毫秒数: {}", end - start);
    }

    /**
     * 获取Jedis对象
     *
     * @return Jedis对象
     */
    public static Jedis getJedis() {
        return JEDIS_POOL.getResource();
    }
}
